package com.github.murzagalin.evaluator.tokenizer

import com.github.murzagalin.evaluator.function.MathFunction
import com.github.murzagalin.evaluator.Tokenizer
import com.github.murzagalin.evaluator.token.Bracket
import com.github.murzagalin.evaluator.token.FunctionCall
import com.github.murzagalin.evaluator.token.Operand
import com.github.murzagalin.evaluator.token.Operator
import kotlin.test.Test
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith

class FunctionsTest {

    private val subject = Tokenizer()

    @Test
    fun simple_functions_with_numbers() {
        MathFunction.ALL.filter { it.argsCount.first == 1 }.forEach { f ->
            assertContentEquals(
                listOf(
                    FunctionCall(1, f),
                    Bracket.Left,
                    Operand.Number(1),
                    Bracket.Right
                ),
                subject.tokenize("${f.name}(1.0)")
            )
        }
    }

    @Test
    fun simple_functions_with_2_numbers() {
        MathFunction.ALL.filter { it.argsCount.first == 2 }.forEach { f ->
            assertContentEquals(
                listOf(
                    FunctionCall(2, f),
                    Bracket.Left,
                    Operand.Number(1),
                    FunctionCall.Delimiter,
                    Operand.Number(2.2),
                    Bracket.Right
                ),
                subject.tokenize("${f.name}(1.0, 2.2)")
            )
        }
    }

    @Test
    fun expressions_with_functions() {
        assertContentEquals(
            listOf(
                Operand.Number(1.0),
                Operator.Plus,
                FunctionCall(1, MathFunction.COS),
                Bracket.Left,
                Operand.Number(1),
                Bracket.Right,
                Operator.Multiplication,
                Operand.Number(5.0)
            ),
            subject.tokenize("1+cos(1.0)*5.0")
        )
        assertContentEquals(
            listOf(
                Bracket.Left,
                Operand.Number(1.0),
                Operator.Division,
                FunctionCall(1, MathFunction.COS),
                Bracket.Left,
                Operand.Number(1.0),
                Bracket.Right,
                Bracket.Right,
                Operator.Power,
                Operand.Number(4.2),
                Operator.Multiplication,
                Operand.Number(5.0)
            ),
            subject.tokenize("(1/cos(1.0))^4.2*5.0")
        )
    }

    @Test
    fun function_in_a_function() {
        assertContentEquals(
            listOf(
                FunctionCall(1, MathFunction.COS),
                Bracket.Left,
                FunctionCall(1, MathFunction.LN),
                Bracket.Left,
                Operand.Number(10.0),
                Bracket.Right,
                Bracket.Right
            ),
            subject.tokenize("cos(ln(10))")
        )
    }

    @Test
    fun function_in_a_function_with_operators() {
        assertContentEquals(
            listOf(
                FunctionCall(1, MathFunction.COS),
                Bracket.Left,
                Operand.Number(2.0),
                Operator.Multiplication,
                FunctionCall(1, MathFunction.LN),
                Bracket.Left,
                Operand.Number(10.0),
                Bracket.Right,
                Operator.Plus,
                Operand.Number(1.0),
                Bracket.Right
            ),
            subject.tokenize("cos(2*ln(10)+1)")
        )
    }


    @Test
    fun non_recognized_function() {
        val nonExistingFunctionName = "fiction_function"
        assertFailsWith<IllegalArgumentException>() {
            subject.tokenize("$nonExistingFunctionName(1.0)")
        }
    }

    @Test
    fun wong_number_of_arguments() {
        assertFailsWith<IllegalArgumentException>() {
            subject.tokenize("log(2.0)")
        }
        assertFailsWith<IllegalArgumentException>() {
            subject.tokenize("cos(1.0, 2.0)")
        }
        assertFailsWith<IllegalArgumentException>() {
            subject.tokenize("min()")
        }
    }

    @Test
    fun correct_number_of_arguments() {
        assertEquals(
            3,
            (subject.tokenize("min(1, 2, 3)")[0] as FunctionCall).argsCount
        )
        assertEquals(
            1,
            (subject.tokenize("cos(1)")[0] as FunctionCall).argsCount
        )
        assertEquals(
            1,
            (subject.tokenize("cos(sin(cos(sin(1))))")[0] as FunctionCall).argsCount
        )
        assertEquals(
            3,
            (subject.tokenize("min(sin(1), max(2, 4, 6, 7), ln(3))")[0] as FunctionCall).argsCount
        )
        assertEquals(
            4,
            (subject.tokenize("max(2, 4, 6, 7)")[0] as FunctionCall).argsCount
        )
    }
}